/*
 * server->cpp
 *
 *  Created on: Nov 13, 2013
 *      Author: killerg
 */

#include "server_deps.h"
#include "server.h"

#include "share/msg_coder.h"
#include "share/cfg_file.h"

#include "share/clock.h"
#include "share/db_mgr.h"
#include "share/net_s.h"
#include "share/connection_mgr.h"
#include "share/util.h"
#include "share/profiler.h"
#include "share/word_filter.h"
#include "share/json/json.h"

#include "gm_cmd_handler.h"
#include "logon_session.h"
#include "client_session.h"
#include "auth_mgr.h"
#include "server_cfg.h"
#include "server_console.h"
#include "bill_session.h"

#include "game_logic/proto_mgr.h"
#include "game_logic/player_mgr.h"
#include "game_logic/occupation_mgr.h"
#include "game_logic/arena.h"
#include "game_logic/rank_mgr.h"
#include "game_logic/game_cfg.h"
#include "game_logic/mail_mgr.h"
#include "game_logic/chat_mgr.h"
#include "game_logic/world_boss.h"
#include "game_logic/pointrank.h"
#include "game_logic/givebackmgr.h"

#include "version.h"

proc_t g_svr;

static void handle_ctrl_c(int signal) {
	g_svr.svcdata->shut = true;
}

static int log_cb(int mask, const char* fmt, ...) {
    va_list arg;
    va_start(arg, fmt);
    int n = g_console->vlog(mask, fmt, arg);
    va_end(arg);
    return n;
}

static int vlog_cb(int mask, const char* fmt, va_list arg) {
    int n = g_console->vlog(mask, fmt, arg);
    return n;
}

bool test_combat(cs::OCombat* ocombat);
bool test_save();
bool pointrank_test();

bool proc_t::init(SvcData* svcdata_) {
    on_signal(SIGINT, handle_ctrl_c);
    sig_ignore(SIGPIPE);

    svcdata = svcdata_;
    status = co::ESS_Initing;

    console = new svr_console_t;

    wordfilter = new WordFilter;
    cfg = new server_cfg_t;
    clock = new my_clock_t;
    game_cfg = new game_cfg_t;
    protomgr = new ProtoMgr;
    db_fast = new db_mgr_t;
    db_slow = new db_mgr_t;
    db_log = new db_mgr_t;
    playermgr = new player_mgr;
    occupation = new occupation_mgr;
    arena = new arena_t;
    rank = new rank_mgr;
    pr = pointrank_create();
    mail = new mailmgr_t;
    worldboss = new world_boss_t;
    logonsess = new logon_session_t;
    bill = new bill_session_t;
    gm_cmd_handler =  new gm_cmd_handler_t;
    sessmgr = new clientsess_mgr_t;
    authmgr = new auth_mgr;
    chat = new chatmgr_t;
    giveback = new giveback_mgr_t;

    if (!cfg->init(svcdata->prefix)) {
        //printf("g_cfg init failed\r\n");
        return false;
    }

    string logname = fmt_cstr("server_%s", svcdata->prefix);
    if (!console->init_log(logname.c_str())) {
    	//printf("g_console log init failed\r\n");
        return false;
    }

    util_logcb = log_cb;
    util_vlogcb = vlog_cb;

    if (!console->init()) {
        UTIL_LOG(E, "console init failed\r\n");
        return false;
    }

    if (!wordfilter->init()) {
        UTIL_LOG(E, "wordfilter init failed\r\n");
        return false;
    }

    UTIL_LOG(I, "branch:%s, commit:%s, server:%d", GIT_BRANCH, GIT_COMMIT_HASH, cfg->ServerId);

    UTIL_LOG(I, "start init");

    if (cfg->DecodeClient) {
        if (!init_code_table()) {
            UTIL_LOG(E, "code_table init failed\r\n");
            return false;
        }
    }

    if (!clock->init(5)) {
        UTIL_LOG(E, "g_clock init failed");
        return false;
    }

    if (!game_cfg->init()) {
        UTIL_LOG(E, "g_game_cfg init failed");
        return false;
    }

    if (!protomgr->init()) {
        UTIL_LOG(E, "g_protomgr init failed\n");
        return false;
    }

    if (!gm_cmd_handler->init()) {
        UTIL_LOG(E, "g_gm_cmd_handler init failed");
        return false;
    }

    if (!db_fast->init(cfg->DbHost, "", "", cfg->DbBucket)) {
        UTIL_LOG(E, "g_db init failed");
        return false;
    }

    if (!db_slow->init(cfg->DbHost, "", "", cfg->DbBucket)) {
        UTIL_LOG(E, "g_db init failed");
        return false;
    }

    if (!db_log->init(cfg->LogDbHost, "", "", cfg->LogDbBucket)) {
        UTIL_LOG(E, "g_db init failed");
        return false;
    }

    if (!playermgr->init()) {
        UTIL_LOG(E, "g_playermgr init failed");
        return false;
    }

    if (!occupation->init()) {
        UTIL_LOG(E, "g_occupation init failed");
        return false;
    }

    if (!arena->init()) {
        UTIL_LOG(E, "g_arena init failed");
        return false;
    }

    if (!rank->init()) {
        UTIL_LOG(E, "g_rank init failed");
        return false;
    }

    if (!pointrank_init(pr)) {
        UTIL_LOG(E, "pointrank_init failed");
        return false;
    }

    if (!mail->init()) {
        UTIL_LOG(E, "g_mail init failed");
        return false;
    }

    if (!worldboss->init()) {
        UTIL_LOG(E, "g_worldboss init failed");
        return false;
    }

    if (!chat->init()) {
        UTIL_LOG(E, "g_chat init failed");
        return false;
    }

    if (!giveback->init()) {
        UTIL_LOG(E, "g_giveback init failed");
        return false;
    }

    if (!logonsess->init()) {
        UTIL_LOG(E, "g_logonsess init failed");
        return false;
    }

    if (!bill->init()) {
    	UTIL_LOG(E, "g_bill init failed");
    	return false;
    }

    if (!sessmgr->init()) {
        UTIL_LOG(E, "g_sessmgr init failed");
        return false;
    }

    if (!console->start_accept(
            cfg->ListenConsoleIp.c_str(),
            cfg->ListenConsolePort)) {
        UTIL_LOG(E, "g_console start_accept init failed at %s:%d",
                cfg->ListenConsoleIp.c_str(),
                cfg->ListenConsolePort);
        return false;
    }

    if (!g_dbf->ok()) {
        UTIL_LOG(E, "_svr_data init failed at %s:%d");
        return false;
    }

    string key = fmt_cstr("_svr_data:%d", cfg->ServerId);
    db_error_t error;
    bool res = g_dbf->get(
    		key.c_str(),
    		[this, key, &error](db_error_t err, const Json::Value &json){
    			error = err;
    			switch (err) {
    			case DBE_Success:
    				server_begin = json["begin"].asInt64();
    				break;
    			}
    		});
    if (!res) {
        UTIL_LOG(E, "server_begin load err init failed");
        return false;
    }
    g_dbf->wait();
    if (error == DBE_NoExist) {
    	server_begin = clock->tt_now;
    	Json::Value json;
    	json["begin"] = Json::Int64(server_begin);
    	bool res = g_dbf->set(key.c_str(), json, [&error](db_error_t err){
    		error = err;
    	});
    	if (!res) {
            UTIL_LOG(E, "server_begin post set err init failed");
            return false;
    	}
    	g_dbf->wait();
    	if (DBE_Success != error) {
            UTIL_LOG(E, "server_begin set cb err init failed");
            return false;
    	}
    }

    UTIL_LOG(I, "listening for console at %s:%d",
            cfg->ListenConsoleIp.c_str(),
            cfg->ListenConsolePort);

// watching ------------------------------------------------------------------------
    for (int i=1; i<ESSTAGENUM; ++i)
        console->watch_n(fmt_cstr("s:%s", ServerStageName[i]), [this, i]()->int{ return count[i]; });

    console->watch_n("client_num", [this](){ return sessmgr->connmgr.size(); }, [](){ return g_cfg->MaxClient; });
    console->watch_n("logon_connected", [this](){ return logonsess->tran.is_connected() ? 1 : 0; });
    console->watch_n("bill_connected", [this](){ return bill->tran.is_connected() ? 1 : 0; });

    console->watch_f("frame_busy", [this]()->double{ return last_total_time; }, [this](){return clock->tick_len;});
    console->watch_n("recv_speed", [this]()->int{ return recv_speed; });
    console->watch_n("send_speed", [this]()->int{ return send_speed; });
    console->watch_n("status", [this]()->int{ return status; });
    console->watch_n("dbf_maxobj", [this]()->int{ return db_fast->max_objsize; });
    console->watch_n("dbs_maxobj", [this]()->int{ return db_slow->max_objsize; });
    console->watch_n("dbf_pending_get", [this](){ return db_fast->pending_get; });
    console->watch_n("dbf_pending_add", [this](){ return db_fast->pending_add; });
    console->watch_n("dbf_pending_set", [this](){ return db_fast->pending_set; });
    console->watch_n("dbs_pending_view", [this](){ return db_slow->pending_view; });
    console->watch_n("dbl_pending_set", [this](){ return db_log->pending_set; });
    console->watch_n("gb_cur", [this](){ return giveback->cur_index; }, [this](){ return giveback->rewards.size(); });

#define WATCH_TIMER(name) console->watch_f(#name "_pct", [this]()->double{ return name##_pct; }, [](){ return 1.0f; })
    WATCH_TIMER(sessmgr);
    WATCH_TIMER(bill);
    WATCH_TIMER(logonsess);
    WATCH_TIMER(authmgr);
    WATCH_TIMER(playermgr);
    WATCH_TIMER(occupation);
    WATCH_TIMER(arena);
    WATCH_TIMER(mail);
    WATCH_TIMER(chat);
    WATCH_TIMER(db_fast);
    WATCH_TIMER(db_slow);
    WATCH_TIMER(db_log);
    WATCH_TIMER(console);
    WATCH_TIMER(rank);
    WATCH_TIMER(pr);
    WATCH_TIMER(worldboss);
#undef WATCH_TIMER

    status = co::ESS_Running;

    UTIL_LOG(I, "end init");

    return true;
}
void proc_t::destroy() {
    UTIL_LOG(I, "start destroy");

    sessmgr->base::destroy();
    bill->destroy();
    giveback->destroy();
    logonsess->destroy();
    gm_cmd_handler->destroy();
    chat->destroy();
    worldboss->destroy();
    mail->destroy();
    pointrank_destroy(pr);
    rank->destroy();
    arena->destroy();
    occupation->destroy();
    playermgr->destroy();
    db_slow->destroy();
    db_fast->destroy();
    db_log->destroy();
    game_cfg->destroy();
    protomgr->destroy();
    clock->destroy();
    cfg->destroy();

    UTIL_LOG(I, "end destroy");

    console->destroy();

    if (cfg->DecodeClient)
        destroy_code_table();

    safe_del(sessmgr);
    safe_del(authmgr);
    safe_del(bill);
    safe_del(logonsess);
    safe_del(giveback);
    safe_del(gm_cmd_handler);
    safe_del(chat);
    safe_del(worldboss);
    safe_del(mail);
    pointrank_delete(pr);
    safe_del(rank);
    safe_del(arena);
    safe_del(occupation);
    safe_del(playermgr);
    safe_del(db_slow);
    safe_del(db_fast);
    safe_del(db_log);
    safe_del(game_cfg);
    safe_del(protomgr);
    safe_del(clock);
    safe_del(cfg);
    safe_del(wordfilter);
    safe_del(console);
    status = co::ESS_Down;
}

void proc_t::loop() {
#define TIMER_START(name) double name##start = clock->get_time()
#define TIMER_END(name)    name##_time += (clock->get_time() - name##start)
#define TIMER_ACC(name) name##_pct = name##_time / (total_time); name##_time = 0

    UTIL_LOG(I, "start loop");

    last_recv = 0;
    last_send = 0;
    recv_speed = 0;
    send_speed = 0;
    while (status == co::ESS_Running || status == co::ESS_Busy || status == co::ESS_Shuting) {
    	if (svcdata->shut) {
    		status = co::ESS_Shuting;
    	}

        clock->begin_update();

        TIMER_START(total);

        if (status == co::ESS_Shuting) {
            sessmgr->disconnect_all();
            if (sessmgr->connmgr.size() == 0
            	&& playermgr->save_queue.size() == 0
            	&& mail->save_queue.size() == 0
            	&& !db_fast->has_pending()
            	&& !db_log->has_pending()
            	&& !bill->db.has_pending()
            	&& bill->need_sync.size() == 0) {
          		break;
            }
        }

        TIMER_START(sessmgr);
        memset(g_svr.count, 0, sizeof(g_svr.count));
        sessmgr->base::update(clock->delta);
        TIMER_END(sessmgr);

        TIMER_START(bill);
        bill->update();
        TIMER_END(bill);

        TIMER_START(logonsess);
        logonsess->update();
        TIMER_END(logonsess);

        TIMER_START(authmgr);
        authmgr->update();
        TIMER_END(authmgr);

        TIMER_START(playermgr);
        playermgr->update();
        TIMER_END(playermgr);

        TIMER_START(occupation);
        occupation->update();
        TIMER_END(occupation);

        TIMER_START(arena);
        arena->update();
        TIMER_END(arena);

        TIMER_START(mail);
		mail->update();
		TIMER_END(mail);

		TIMER_START(worldboss);
		worldboss->update();
		TIMER_END(worldboss);


        TIMER_START(chat);
        chat->update();
        TIMER_END(chat);

        TIMER_START(db_fast);
        db_fast->update();
        TIMER_END(db_fast);

        TIMER_START(db_slow);
        db_slow->update();
        TIMER_END(db_slow);

        TIMER_START(db_log);
        db_log->update();
        TIMER_END(db_log);

        TIMER_START(console);
        console->update();
        TIMER_END(console);

        TIMER_START(rank);
        rank->update();
        TIMER_END(rank);

        TIMER_START(pr);
        pointrank_update(pr);
        TIMER_END(pr);

        TIMER_END(total);

        giveback->update();

        clock->end_update();
        clock->wait_frame();

        static double ptimer = 0.0f;
        ptimer += clock->delta;
        if (ptimer >= 1.0f) {
            recv_speed = (sessmgr->base::net.bytes_recv - last_recv) / ptimer;
            send_speed = (sessmgr->base::net.bytes_send - last_send) / ptimer;
            TIMER_ACC(sessmgr);
            TIMER_ACC(bill);
            TIMER_ACC(logonsess);
            TIMER_ACC(authmgr);
            TIMER_ACC(playermgr);
            TIMER_ACC(occupation);
            TIMER_ACC(arena);
            TIMER_ACC(mail);
            TIMER_ACC(worldboss);
            TIMER_ACC(chat);
            TIMER_ACC(db_fast);
            TIMER_ACC(db_slow);
            TIMER_ACC(db_log);
            TIMER_ACC(console);
            TIMER_ACC(rank);
            TIMER_ACC(pr);

            if (status != co::ESS_Shuting) {
                switch (status) {
                case co::ESS_Running:
                    if (total_time >= 0.4 || send_speed >= 5000000 || sessmgr->connmgr.size() >= g_cfg->MaxClient)
                        status = co::ESS_Busy;
                    break;
                case co::ESS_Busy:
                    if (total_time < 0.2 && send_speed < 5000000 && sessmgr->connmgr.size() < g_cfg->MaxClient)
                        status = co::ESS_Running;
                    break;
                }
            }

            last_total_time = total_time;
            total_time = 0;

            last_recv = sessmgr->base::net.bytes_recv;
            last_send = sessmgr->base::net.bytes_send;
            ptimer = 0.0f;
        }
        svcdata->refresh = g_clock->tt_now;
    }

    UTIL_LOG(I, "end loop");
}
